using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

/// <summary>
/// A geographical coordinate for use in a SQL Server database.
/// </summary>
[Serializable]
[Microsoft.SqlServer.Server.SqlUserDefinedType(Format.Native)]
public struct LatLongPoint : INullable
{
    public LatLongPoint(double latitude, double longitude)
    {
        _latitude = latitude;
        _longitude = longitude;
        m_Null = false;
    }

    public override string ToString()
    {
        // Replace the following code with your code
        return (@"{" + _latitude + "," + _longitude + @"}");
    }

    public bool IsNull
    {
        get
        {
            // Put your code here
            return m_Null;
        }
    }

    public static LatLongPoint Null
    {
        get
        {
            LatLongPoint h = new LatLongPoint();
            h.m_Null = true;
            return h;
        }
    }

    public static LatLongPoint Parse(SqlString s)
    {
        if (s.IsNull)
            return Null;
        LatLongPoint u = new LatLongPoint();
        
        string str = (string)s;
        str = str.Replace("{", "").Replace("}", "");
        string[] parts = str.Split(',');
        if (parts.Length != 2)
            throw new ArgumentException("A LatLongPoint must be in the format \"{ lat, long }\".");

        if (!double.TryParse(parts[0].Trim(), out u._latitude))
            throw new ArgumentException("A LatLongPoint must be in the format \"{ lat, long }\".");

        if (!double.TryParse(parts[1].Trim(), out u._longitude))
            throw new ArgumentException("A LatLongPoint must be in the format \"{ lat, long }\".");

        return u;
    }

    /// <summary>
    /// Gets or sets the latitude of the point
    /// </summary>
    public double Latitude
    {
        get
        {
            return _latitude;
        }
        set
        {
            _longitude = value;
        }
    }

    /// <summary>
    /// Gets or sets the longitude of the point.
    /// </summary>
    public double Longitude
    {
        get
        {
            return _longitude;
        }
        set
        {
            _longitude = value;
        }
    }


    private double _latitude;

    private double _longitude;

    private bool m_Null;

    /// <summary>
    /// Gets the distance between this point and another point.
    /// </summary>
    /// <param name="point">The point to get the distance from.</param>
    /// <returns>The distance from one point to another in kilometers</returns>
    [SqlMethod]
    public double DistanceFrom(LatLongPoint point)
    {
        if (point.Equals(this))
            return 0.0d;

        // All calculations done in KM.
        double dLat = point._latitude - this._latitude;
        double dLong = point._longitude - this._longitude;

        double a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) +
                Math.Cos(this._latitude) * Math.Cos(point._latitude) * Math.Sin(dLong / 2) * Math.Sin(dLong / 2);
        double c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a));
        double d = 6371 * c;

        return  Math.Abs(d);
    }

    public override bool Equals(object obj)
    {
        if (obj == null)
            return false;

        if (obj.GetType() != typeof(LatLongPoint))
            return false;

        LatLongPoint p = (LatLongPoint)obj;

        return (p._latitude == this._latitude && p._longitude == this._longitude);
    }

    public override int GetHashCode()
    {
        return _latitude.GetHashCode() & _longitude.GetHashCode();
    }
}


